package com.connector.base.server;

import com.connector.base.codec.PacketDecoder;
import com.connector.base.codec.PacketEncoder;
import com.connector.base.exception.ServerException;
import com.connector.base.service.BaseService;
import com.connector.base.service.IServer;
import com.google.common.base.Strings;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.spi.SelectorProvider;
import java.util.concurrent.ThreadFactory;

public abstract class NettyTcpServer extends BaseService implements IServer
{
    private static final Logger log;
    private static final String BOSS_THREAD_NAME = "connector_server_boss";
    private static final String WORKER_THREAD_NAME = "connector_server_worker";
    private static final boolean DEFAULT_USE_EPOLL = false;
    private final String host;
    private final int port;
    private boolean useEpoll;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    public NettyTcpServer(final int port) {
        this.useEpoll = false;
        this.host = null;
        this.port = port;
    }

    public NettyTcpServer(final String host, final int port) {
        this.useEpoll = false;
        this.host = host;
        this.port = port;
    }

    @Override
    public void init() {
        if (this.started.get()) {
            NettyTcpServer.log.error("server already started");
            throw new ServerException("server already started");
        }
    }

    @Override
    public void start() {
        if (this.useEpoll) {
            this.createEpollServer();
        }
        else {
            this.createNioServer();
        }
    }

    @Override
    public void stop() {
        if (!this.started.get()) {
            NettyTcpServer.log.error("server already stopped");
            throw new ServerException("server already stopped");
        }
        NettyTcpServer.log.info("try shutdown {}", (Object)this.getClass().getSimpleName());
        if (this.bossGroup != null) {
            this.bossGroup.shutdownGracefully().syncUninterruptibly();
        }
        if (this.workerGroup != null) {
            this.workerGroup.shutdownGracefully().syncUninterruptibly();
        }
        NettyTcpServer.log.info("{} shutdown success", (Object)this.getClass().getSimpleName());
    }

    private void createNioServer() {
        EventLoopGroup bossGroup = this.getBossGroup();
        EventLoopGroup workerGroup = this.getWorkerGroup();
        if (bossGroup == null) {
            final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(this.getBossThreadNum(), this.getBossThreadFactory(), this.getSelectorProvider());
            nioEventLoopGroup.setIoRatio(100);
            bossGroup = (EventLoopGroup)nioEventLoopGroup;
        }
        if (workerGroup == null) {
            final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(this.getWorkThreadNum(), this.getWorkThreadFactory(), this.getSelectorProvider());
            nioEventLoopGroup.setIoRatio(this.getIoRate());
            workerGroup = (EventLoopGroup)nioEventLoopGroup;
        }
        this.createServer(bossGroup, workerGroup, this.getChannelFactory());
    }

    private void createEpollServer() {
        EventLoopGroup bossGroup = this.getBossGroup();
        EventLoopGroup workerGroup = this.getWorkerGroup();
        if (bossGroup == null) {
            final EpollEventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup(this.getBossThreadNum(), this.getBossThreadFactory());
            epollEventLoopGroup.setIoRatio(100);
            bossGroup = (EventLoopGroup)epollEventLoopGroup;
        }
        if (workerGroup == null) {
            final EpollEventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup(this.getWorkThreadNum(), this.getWorkThreadFactory());
            epollEventLoopGroup.setIoRatio(this.getIoRate());
            workerGroup = (EventLoopGroup)epollEventLoopGroup;
        }
        this.createServer(bossGroup, workerGroup, (ChannelFactory<? extends ServerChannel>)EpollServerSocketChannel::new);
    }

    private void createServer(final EventLoopGroup bossGroup, final EventLoopGroup workerGroup, final ChannelFactory<? extends ServerChannel> channelFactory) {
        this.bossGroup = bossGroup;
        this.workerGroup = workerGroup;
        try {
            final ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup);
            b.channelFactory((ChannelFactory)channelFactory);
            b.childHandler((ChannelHandler)new ChannelInitializer() {
                public void initChannel(final Channel ch) throws Exception {
                    NettyTcpServer.this.initPipeline(ch.pipeline());
                }
            });
            this.initOptions(b);
            final InetSocketAddress address = Strings.isNullOrEmpty(this.host) ? new InetSocketAddress(this.port) : new InetSocketAddress(this.host, this.port);
            final Channel ch = b.bind((SocketAddress)address).sync().channel();
            if (this.started.compareAndSet(false, true)) {
                NettyTcpServer.log.info("server started at port {}", (Object)this.port);
                ch.closeFuture().sync();
            }
            else {
                ch.close();
            }
        }
        catch (Exception e) {
            NettyTcpServer.log.error("server start exception", (Throwable)e);
            throw new ServerException(e);
        }
    }

    protected void initPipeline(final ChannelPipeline pipeline) {
        pipeline.addLast("decoder", this.getDecoder());
        pipeline.addLast("encoder", this.getEncoder());
        pipeline.addLast("handler", this.getChannelHandler());
    }

    protected void initOptions(final ServerBootstrap b) {
        b.option(ChannelOption.ALLOCATOR,  PooledByteBufAllocator.DEFAULT);
        b.childOption(ChannelOption.ALLOCATOR,  PooledByteBufAllocator.DEFAULT);
    }

    protected ChannelHandler getDecoder() {
        return  new PacketDecoder();
    }

    public SelectorProvider getSelectorProvider() {
        return SelectorProvider.provider();
    }

    protected ThreadFactory getBossThreadFactory() {
        return (ThreadFactory)new DefaultThreadFactory("connector_server_boss");
    }

    protected ThreadFactory getWorkThreadFactory() {
        return (ThreadFactory)new DefaultThreadFactory("connector_server_worker");
    }

    protected int getBossThreadNum() {
        return 1;
    }

    protected int getWorkThreadNum() {
        return 0;
    }

    protected int getIoRate() {
        return 70;
    }

    public ChannelFactory<? extends ServerChannel> getChannelFactory() {
        return (ChannelFactory<? extends ServerChannel>)NioServerSocketChannel::new;
    }

    protected ChannelHandler getEncoder() {
        return (ChannelHandler)new PacketEncoder();
    }

    public abstract ChannelHandler getChannelHandler();

    public String getHost() {
        return this.host;
    }

    public int getPort() {
        return this.port;
    }

    public boolean isUseEpoll() {
        return this.useEpoll;
    }

    public EventLoopGroup getBossGroup() {
        return this.bossGroup;
    }

    public EventLoopGroup getWorkerGroup() {
        return this.workerGroup;
    }

    static {
        log = LoggerFactory.getLogger((Class) NettyTcpServer.class);
    }
}
